﻿using System.Globalization;
using System.IO;
using System.Text;

namespace ICommonLibrary.Json {

    public class JsonUtil {

        public static string Encode(string value) {
            return Encode(value, '"', true);
        }

        public static string Encode(string value, char delimiter, bool appendDelimiters) {
            using (StringWriter w = CreateStringWriter(value == null ? 16 : value.Length)) {
                Escape(w, value, delimiter, appendDelimiters);
                return w.ToString();
            }
        }

        private static void Escape(TextWriter writer, string value, char delimiter, bool appendDelimiters) {
            // leading delimiter
            if (appendDelimiters)
                writer.Write(delimiter);

            if (value != null) {
                int lastWritePosition = 0;
                int skipped = 0;
                char[] chars = null;

                for (int i = 0; i < value.Length; i++) {
                    char c = value[i];
                    string escapedValue;

                    switch (c) {
                        case '\t':
                        escapedValue = @"\t";
                        break;
                        case '\n':
                        escapedValue = @"\n";
                        break;
                        case '\r':
                        escapedValue = @"\r";
                        break;
                        case '\f':
                        escapedValue = @"\f";
                        break;
                        case '\b':
                        escapedValue = @"\b";
                        break;
                        case '\\':
                        escapedValue = @"\\";
                        break;
                        case '\u0085': // Next Line
                        escapedValue = @"\u0085";
                        break;
                        case '\u2028': // Line Separator
                        escapedValue = @"\u2028";
                        break;
                        case '\u2029': // Paragraph Separator
                        escapedValue = @"\u2029";
                        break;
                        case '\'':
                        // only escape if this charater is being used as the delimiter
                        escapedValue = (delimiter == '\'') ? @"\'" : null;
                        break;
                        case '"':
                        // only escape if this charater is being used as the delimiter
                        escapedValue = (delimiter == '"') ? "\\\"" : null;
                        break;
                        default:
                        escapedValue = (c <= '\u001f') ? ToCharAsUnicode(c) : null;
                        break;
                    }

                    if (escapedValue != null) {
                        if (chars == null)
                            chars = value.ToCharArray();

                        // write skipped text
                        if (skipped > 0) {
                            writer.Write(chars, lastWritePosition, skipped);
                            skipped = 0;
                        }

                        // write escaped value and note position
                        writer.Write(escapedValue);
                        lastWritePosition = i + 1;
                    }
                    else {
                        skipped++;
                    }
                }

                // write any remaining skipped text
                if (skipped > 0) {
                    if (lastWritePosition == 0)
                        writer.Write(value);
                    else
                        writer.Write(chars, lastWritePosition, skipped);
                }
            }

            // trailing delimiter
            if (appendDelimiters)
                writer.Write(delimiter);
        }

        private static string ToCharAsUnicode(char c) {
            char h1 = IntToHex((c >> 12) & '\x000f');
            char h2 = IntToHex((c >> 8) & '\x000f');
            char h3 = IntToHex((c >> 4) & '\x000f');
            char h4 = IntToHex(c & '\x000f');

            return new string(new[] { '\\', 'u', h1, h2, h3, h4 });
        }

        private static StringWriter CreateStringWriter(int capacity) {
            return new StringWriter(new StringBuilder(capacity), CultureInfo.InvariantCulture);
        }

        private static char IntToHex(int n) {
            if (n <= 9) {
                return (char)(n + 48);
            }
            return (char)((n - 10) + 97);
        }
    }
}